var LineTracker2 = (function (){
    function LineTracker(){
        this.reset();
    }

    LineTracker.prototype.reset = function (){
        var node = new LineNode(0, '');
        node.offset = 0;
        node.index = 0;
        this._quickTable = [node];
        this._quickLength = 1;
        this._count = 1;
    };

    LineTracker.prototype.setString = function (string){
        this.reset();
        this.spliceString(0, 0, string);
    };

    LineTracker.prototype.spliceString = function (offset, length, string){
        var endOffset = offset + length;
        var head = this.nodeByOffset(offset);
        var tail = this.nodeByOffset(endOffset);
        var quickLength = head.index + 1;
        var deleteCount = tail.index - head.index;
        var toInsert = this._parseNodes(string);
        var insertCount = toInsert.count - 1;
        if (head === tail) {
            tail = new LineNode(head.offset + head.length - endOffset, head.delimiter);
            tail.next = head.next;
        } else {
            tail.length = tail.offset + tail.length - endOffset;
        }
        head.delimiter = '';
        head.length = offset - head.offset;
        var toInsertHead = toInsert.next;
        var toInsertTail = toInsert.last;
        if (toInsertHead === toInsertTail) {
            if (toInsertHead.delimiter) {
                head.length += toInsertHead.length;
                head.delimiter = toInsertHead.delimiter;
                head.next = tail;
            } else {
                head.length += toInsertHead.length + tail.length;
                head.delimiter = tail.delimiter;
                head.next = tail.next;
            }
        } else {
            head.length += toInsertHead.length;
            head.delimiter = toInsertHead.delimiter;
            head.next = toInsertHead.next;
            toInsertTail.length += tail.length;
            toInsertTail.delimiter = tail.delimiter;
            toInsertTail.next = tail.next;
        }
        this._count += insertCount - deleteCount;
        this._quickLength = quickLength;
        if (this._quickTable.length - quickLength > 1024) {
            this._quickTable = this._quickTable.slice(0, quickLength);
        }
        return [head.index, deleteCount, insertCount];
    };

    var regex = /\r?\n|\r/g;
    var handle = {
        next: null,
        last: null,
        count: 0
    };
    LineTracker.prototype._parseNodes = function (string){
        handle.count = 0;
        var match;
        var lastIndex = 0;
        regex.lastIndex = 0;
        var node = handle;
        while ((match = regex.exec(string))) {
            node.next = new LineNode(regex.lastIndex - lastIndex, match[0]);
            node = node.next;
            handle.count ++;
            lastIndex = regex.lastIndex;
        }
        node.next = new LineNode(string.length - lastIndex, '');
        handle.last = node.next;
        handle.count ++;
        return handle;
    };

    LineTracker.prototype.nodeByOffset = function (offset){
        var quickTable = this._quickTable;
        var quickLength = this._quickLength;
        var lastIndex = quickLength - 1;
        var node = quickTable[lastIndex];
        if (offset <= node.offset + node.length - node.delimiter.length) {
            var left = 0;
            var right = quickLength;
            while (left < right) {
                var middle = (left + right) >> 1;
                node = quickTable[middle];
                if (offset > node.offset + node.length - node.delimiter.length) {
                    left = middle + 1;
                } else {
                    right = middle;
                }
            }
            node = quickTable[left];
        } else {
            var nodeOffset = node.offset;
            while (nodeOffset < offset && node.next) {
                nodeOffset += node.length;
                node = node.next;
                node.offset = nodeOffset;
                ++ lastIndex;
                node.index = lastIndex;
                quickTable[lastIndex] = node;
            }
            this._quickLength = lastIndex + 1;
        }
        return node;
    };

    LineTracker.prototype.getLineCount = function (){
        return this._count;
    };

    LineTracker.prototype.getLineNode = function (index){
        var quickTable = this._quickTable;
        var quickLength = this._quickLength;
        if (index < quickLength) {
            return quickTable[index];
        }
        var nodeIndex = quickLength - 1;
        var node = this._quickTable[nodeIndex];
        var nodeOffset = node.offset;
        while (nodeIndex < index){
            nodeOffset += node.length;
            node = node.next;
            ++ nodeIndex;
            node.index = nodeIndex;
            node.offset = nodeOffset;
            quickTable[nodeIndex] = node;
        }
        this._quickLength = nodeIndex + 1;
        return node;
    };

    LineTracker.prototype.getLineOffset = function (index){
        return this.getLineNode(index).offset;
    };

    LineTracker.prototype.getLineLength = function (index, includeDelimiter){
        var node = this.getLineNode(index);
        return includeDelimiter ? node.length : node.length - node.delimiter.length;
    };

    function LineNode(length, delimiter){
        this.length = length;
        this.delimiter = delimiter;
    }

    LineNode.prototype.next = null;
    LineNode.prototype.index = 0;
    LineNode.prototype.offset = 0;

    return LineTracker;
})();
